package net.tv90.volunteerserveradmin.service;

import cn.dev33.satoken.stp.StpUtil;
import jakarta.annotation.Resource;
import jakarta.persistence.criteria.Predicate;
import lombok.extern.slf4j.Slf4j;
import net.tv90.volunteerserveradmin.dao.StudentRepository;
import net.tv90.volunteerserveradmin.dao.TeacherRepository;
import net.tv90.volunteerserveradmin.dao.UserRepository;
import net.tv90.volunteerserveradmin.exception.ServiceException;
import net.tv90.volunteerserveradmin.manager.MailManager;
import net.tv90.volunteerserveradmin.manager.SMSCodeManager;
import net.tv90.volunteerserveradmin.model.domain.Student;
import net.tv90.volunteerserveradmin.model.domain.Teacher;
import net.tv90.volunteerserveradmin.model.domain.User;
import net.tv90.volunteerserveradmin.model.dto.*;
import net.tv90.volunteerserveradmin.model.vo.UserStatusVO;
import net.tv90.volunteerserveradmin.model.vo.UserVO;
import net.tv90.volunteerserveradmin.utils.RedisUtils;
import net.tv90.volunteerserveradmin.utils.entity.Assert;
import net.tv90.volunteerserveradmin.utils.entity.PageResult;
import net.tv90.volunteerserveradmin.utils.SecurityUtils;
import net.tv90.volunteerserveradmin.utils.entity.SMSCode;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class UserService {

    @Resource
    private UserRepository userRepository;
    @Resource
    private TeacherRepository teacherRepository;
    @Resource
    private StudentRepository studentRepository;
    @Resource
    private SMSCodeManager smsCodeManager;
    @Resource
    private MailManager mailManager;
    @Resource
    private RedisUtils redisUtils;

    public UserStatusVO login(String usernameOrEmail, String password) {
        Assert.assertNotNull("用户名或邮箱不能为空", HttpStatus.BAD_REQUEST, usernameOrEmail);
        Assert.assertNotNull("密码不能为空", HttpStatus.BAD_REQUEST, password);
        String encryptedPwd = SecurityUtils.sha256Encrypt(password);
        User user = userRepository.findUserByUsernameAndPassword(usernameOrEmail, encryptedPwd);
        if (user == null) {
            user = userRepository.findUserByEmailAndPassword(usernameOrEmail, encryptedPwd);
        }
        Assert.assertNotNull("用户名/邮箱或密码错误", HttpStatus.BAD_REQUEST, user);
        log.info("{} 登录成功", user.getUsername());
        StpUtil.login(user.getId());
        if (user.getRole() != 0) {
            if (user.getRole() == 1) {
                Student student = studentRepository.findById(user.getRoleId())
                        .orElseThrow(() -> new ServiceException("该用户不存在", HttpStatus.BAD_REQUEST));
                return UserStatusVO.builder()
                        .role(user.getRole())
                        .name(student.getName())
                        .code(student.getStudentNumber())
                        .id(student.getId())
                        .build();
            }
            if (user.getRole() == 2) {
                Teacher teacher = teacherRepository.findById(user.getRoleId())
                        .orElseThrow(() -> new ServiceException("该用户不存在", HttpStatus.BAD_REQUEST));
                return UserStatusVO.builder()
                        .role(user.getRole())
                        .name(teacher.getName())
                        .code(teacher.getStaffNumber())
                        .id(teacher.getId())
                        .build();
            }
        }
        return UserStatusVO.builder()
                .role(user.getRole())
                .build();
    }


    public UserStatusVO getUserStatus() {
        if (!StpUtil.isLogin()) {
            return UserStatusVO.builder()
                    .role(0)
                    .build();
        }
        long id = StpUtil.getLoginIdAsLong();
        // 缓存的 Redis Key
        String redisUserKey = "user::status::" + id;
        // 从 Redis 获取用户信息
        User user = redisUtils.getObject(redisUserKey);
        // 如果 Redis 中没有用户信息，从数据库查询并缓存
        if (user == null) {
            user = userRepository.findById(id)
                    .orElseThrow(() -> new ServiceException("该用户不存在", HttpStatus.OK));
            // 设置缓存过期时间为30分钟
            redisUtils.setObject(redisUserKey, user, 30, TimeUnit.MINUTES);
        }
        if (user.getRole() != 0) {
            // 学生角色
            if (user.getRole() == 1) {
                String studentRedisKey = "student::status::" + user.getRoleId();
                Student student = redisUtils.getObject(studentRedisKey);
                if (student == null) {
                    student = studentRepository.findById(user.getRoleId())
                            .orElseThrow(() -> new ServiceException("该用户不存在", HttpStatus.OK));
                    // 缓存学生信息
                    redisUtils.setObject(studentRedisKey, student, 30, TimeUnit.MINUTES);
                }
                return UserStatusVO.builder()
                        .role(user.getRole())
                        .name(student.getName())
                        .code(student.getStudentNumber())
                        .id(student.getId())
                        .build();
            }
            // 教师角色
            if (user.getRole() == 2) {
                String teacherRedisKey = "teacher::status::" + user.getRoleId();
                Teacher teacher = redisUtils.getObject(teacherRedisKey);
                if (teacher == null) {
                    teacher = teacherRepository.findById(user.getRoleId())
                            .orElseThrow(() -> new ServiceException("该用户不存在", HttpStatus.OK));
                    // 缓存教师信息
                    redisUtils.setObject(teacherRedisKey, teacher, 30, TimeUnit.MINUTES);
                }
                return UserStatusVO.builder()
                        .role(user.getRole())
                        .name(teacher.getName())
                        .code(teacher.getStaffNumber())
                        .id(teacher.getId())
                        .build();
            }
        }
        return UserStatusVO.builder()
                .role(user.getRole())
                .build();
    }

    @Transactional(rollbackFor = Exception.class)
    public void register(UserRegisterDTO dto) {
        Assert.assertNotNull("用户名不能为空", HttpStatus.OK, dto.getUsername());
        Assert.assertNotNull("密码不能为空", HttpStatus.OK, dto.getPassword());
        Assert.assertNotNull("邮箱不能为空", HttpStatus.OK, dto.getEmail());
        boolean checkCode = smsCodeManager.checkCodeForEmail(new SMSCode(dto.getEmail(), dto.getSmsCode()));
        if (!checkCode) {
            throw new ServiceException("邮箱验证码错误");
        }
        User user = userRepository.findUserByUsernameOrEmail(dto.getUsername(), dto.getEmail());
        Assert.assertNull("该用户名或邮箱已存在", HttpStatus.OK, user);
        log.info("{}注册成功", dto.getUsername());
        User registered = User.builder()
                .username(dto.getUsername())
                .password(SecurityUtils.sha256Encrypt(dto.getPassword()))
                .email(dto.getEmail())
                .role(0)
                .roleId(null)
                .build();
        userRepository.save(registered);
    }

    public void logout() {
        long id = StpUtil.getLoginIdAsLong();
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ServiceException("该用户不存在", HttpStatus.OK));
        log.info("{}登出成功", user.getUsername());
        deleteUserCache(id);
        StpUtil.kickout(user.getId());
    }

    @Transactional(rollbackFor = Exception.class)
    public void verify(UserVerifyDTO dto) {
        long id = StpUtil.getLoginIdAsLong();
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ServiceException("该用户不存在", HttpStatus.OK));
        if (user.getRole() != 0) {
            throw new ServiceException("该用户已经认证", HttpStatus.OK);
        }
        switch (dto.getRole()) {
            //学生
            case 1: {
                Student student = studentRepository.findByStudentNumberAndNameAndIdCardNumber(dto.getCode(), dto.getName(), dto.getIdCardNumber());
                Assert.assertNotNull("该学生不存在，请检查提交信息", HttpStatus.OK, student);
                user.setRole(1);
                user.setRoleId(student.getId());
                student.setUserId(user.getId());
                mailManager.sendIdentifyMailTo(user.getEmail(), user.getUsername());
                userRepository.save(user);
                studentRepository.save(student);
                deleteUserCache(id);
                deleteStudentCache(id);
                break;
            }
            //老师
            case 2: {
                Teacher teacher = teacherRepository.findByStaffNumberAndNameAndIdCardNumber(dto.getCode(), dto.getName(), dto.getIdCardNumber());
                Assert.assertNotNull("该教师不存在，请检查提交信息", HttpStatus.OK, teacher);
                user.setRole(2);
                user.setRoleId(teacher.getId());
                teacher.setUserId(user.getId());
                mailManager.sendIdentifyMailTo(user.getEmail(), user.getUsername());
                userRepository.save(user);
                teacherRepository.save(teacher);
                deleteUserCache(id);
                deleteTeacherCache(id);
                break;
            }
            default:
                throw new ServiceException("无效的身份角色代码", HttpStatus.BAD_REQUEST);
        }
    }

    public User getById(Long loginId) {
        return userRepository.findById(loginId).orElseThrow();
    }

    /**
     * 获取用户信息通过ID
     */
    public UserVO getUserById(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ServiceException("用户不存在", HttpStatus.NOT_FOUND));
        return new UserVO(user);
    }

    /**
     * 获取所有用户，支持分页和条件查询
     */
    public PageResult<UserVO> queryUsers(UserQueryDTO dto) {
        if (dto.getPage() == null || dto.getPage() < 1) {
            dto.setPage(1);
        }
        if (dto.getPageNum() == null || dto.getPageNum() < 10 || dto.getPageNum() > 100) {
            dto.setPageNum(10);
        }
        if (dto.getOrderBy() == null || dto.getOrderBy().isEmpty()) {
            dto.setOrderBy("id");
        }
        if (dto.getDesc() == null) {
            dto.setDesc(0);
        }

        // 构建排序规则
        Sort sort = dto.getDesc() == 1 ? Sort.by(dto.getOrderBy()).descending() : Sort.by(dto.getOrderBy()).ascending();
        PageRequest pageRequest = PageRequest.of(dto.getPage() - 1, dto.getPageNum(), sort);

        // 构建 Specification
        Specification<User> spec = (root, query, criteriaBuilder) -> {
            Predicate predicate = criteriaBuilder.conjunction();

            if (dto.getUsername() != null && !dto.getUsername().isEmpty()) {
                predicate = criteriaBuilder.and(predicate, criteriaBuilder.like(root.get("username"), "%" + dto.getUsername() + "%"));
            }

            if (dto.getEmail() != null && !dto.getEmail().isEmpty()) {
                predicate = criteriaBuilder.and(predicate, criteriaBuilder.like(root.get("email"), "%" + dto.getEmail() + "%"));
            }

            if (dto.getRole() != null) {
                predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("role"), dto.getRole()));
            }

            return predicate;
        };

        Page<User> userPage = userRepository.findAll(spec, pageRequest);
        List<UserVO> list = userPage.getContent().stream()
                .map(UserVO::new)
                .collect(Collectors.toList());

        return new PageResult<>(list, userPage.getTotalElements());
    }

    /**
     * 添加新用户
     */
    @Transactional(rollbackFor = Exception.class)
    public void addUser(UserDTO dto) {
        Assert.assertNotNull("用户名不能为空", HttpStatus.BAD_REQUEST, dto.getUsername());
        Assert.assertNotNull("密码不能为空", HttpStatus.BAD_REQUEST, dto.getPassword());
        Assert.assertNotNull("邮箱不能为空", HttpStatus.BAD_REQUEST, dto.getEmail());

        if (userRepository.existsByUsername(dto.getUsername())) {
            throw new ServiceException("用户名已存在", HttpStatus.CONFLICT);
        }

        if (userRepository.existsByEmail(dto.getEmail())) {
            throw new ServiceException("邮箱已存在", HttpStatus.CONFLICT);
        }

        User user = new User();
        user.setUsername(dto.getUsername());
        user.setPassword(SecurityUtils.sha256Encrypt(dto.getPassword()));
        user.setPhoneNumber(dto.getPhoneNumber());
        user.setEmail(dto.getEmail());
        user.setRole(dto.getRole());
        user.setRoleId(dto.getRoleId());

        userRepository.save(user);
    }

    /**
     * 更新用户信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(Long id, UserDTO dto) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ServiceException("用户不存在", HttpStatus.NOT_FOUND));

        if (dto.getUsername() != null && !dto.getUsername().isEmpty() && !Objects.equals(user.getUsername(), dto.getUsername())) {
            if (userRepository.existsByUsername(dto.getUsername())) {
                throw new ServiceException("用户名已存在", HttpStatus.CONFLICT);
            }
            user.setUsername(dto.getUsername());
        }

        if (dto.getEmail() != null && !dto.getEmail().isEmpty() && !Objects.equals(user.getEmail(), dto.getEmail())) {
            if (userRepository.existsByEmail(dto.getEmail())) {
                throw new ServiceException("邮箱已存在", HttpStatus.CONFLICT);
            }
            user.setEmail(dto.getEmail());
        }

        if (dto.getPassword() != null && !dto.getPassword().isEmpty()) {
            user.setPassword(SecurityUtils.sha256Encrypt(dto.getPassword()));
        }

        if (dto.getPhoneNumber() != null) {
            user.setPhoneNumber(dto.getPhoneNumber());
        }

        if (dto.getRole() != null) {
            user.setRole(dto.getRole());
        }

        if (dto.getRoleId() != null) {
            user.setRoleId(dto.getRoleId());
        }

        deleteUserCache(id);
        userRepository.save(user);
    }

    /**
     * 删除用户
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ServiceException("用户不存在", HttpStatus.NOT_FOUND));

        // 处理关联的 student 或 teacher，将 userId 置空
        if (user.getRole() != null && user.getRoleId() != null) {
            if (user.getRole() == 1) {
                studentRepository.findById(user.getRoleId()).ifPresent(student -> {
                    student.setUserId(null);
                    studentRepository.save(student);
                });
            } else if (user.getRole() == 2) {
                teacherRepository.findById(user.getRoleId()).ifPresent(teacher -> {
                    teacher.setUserId(null);
                    teacherRepository.save(teacher);
                });
            }
        }

        // 删除用户
        deleteUserCache(id);
        userRepository.delete(user);
    }

    public void changePassword(String oldPassword, String newPassword) {
        Assert.assertNotNull("旧密码不能为空", HttpStatus.BAD_REQUEST, oldPassword);
        Assert.assertNotNull("新密码不能为空", HttpStatus.BAD_REQUEST, newPassword);
        Assert.assertTrue("新密码长度必须在6到20位之间", HttpStatus.BAD_REQUEST,
                newPassword.length() >= 6 && newPassword.length() <= 20);

        Long userId = StpUtil.getLoginIdAsLong();
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ServiceException("用户不存在", HttpStatus.NOT_FOUND));

        String encryptedOldPassword = SecurityUtils.sha256Encrypt(oldPassword);
        if (!Objects.equals(user.getPassword(), encryptedOldPassword)) {
            throw new ServiceException("旧密码错误", HttpStatus.FORBIDDEN);
        }

        String encryptedNewPassword = SecurityUtils.sha256Encrypt(newPassword);
        user.setPassword(encryptedNewPassword);

        userRepository.save(user);
        deleteUserCache(userId);
        log.info("用户 {} 成功修改密码", user.getUsername());
    }

    @Transactional(rollbackFor = Exception.class)
    public void resetPassword(ResetPasswordDTO dto) {
        // 校验参数是否为空
        Assert.assertNotNull("邮箱不能为空", HttpStatus.BAD_REQUEST, dto.getEmail());
        Assert.assertNotNull("验证码不能为空", HttpStatus.BAD_REQUEST, dto.getCode());
        Assert.assertNotNull("新密码不能为空", HttpStatus.BAD_REQUEST, dto.getNewPassword());
        Assert.assertTrue("新密码长度必须在6到20位之间", HttpStatus.BAD_REQUEST,
                dto.getNewPassword().length() >= 6 && dto.getNewPassword().length() <= 20);
        // 校验邮箱验证码
        boolean isCodeValid = smsCodeManager.checkCodeForEmail(new SMSCode(dto.getEmail(), dto.getCode()));
        if (!isCodeValid) {
            throw new ServiceException("邮箱验证码无效或已过期", HttpStatus.BAD_REQUEST);
        }
        // 查询用户信息
        User user = userRepository.findByEmail(dto.getEmail());
        Assert.assertNotNull("该邮箱未注册", HttpStatus.NOT_FOUND, user);
        // 更新密码
        String encryptedNewPassword = SecurityUtils.sha256Encrypt(dto.getNewPassword());
        user.setPassword(encryptedNewPassword);
        userRepository.save(user);
        // 清除缓存中的用户信息
        deleteUserCache(user.getId());
        // 日志记录
        log.info("用户 {} 通过找回密码功能成功重置密码", user.getUsername());
    }

    public UserVO getUserSelfById() {
        Long userId = StpUtil.getLoginIdAsLong();
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ServiceException("用户不存在", HttpStatus.NOT_FOUND));
        return new UserVO(user);
    }

    public void deleteUserCache(Long userId) {
        String redisUserKey = "user::status::" + userId;
        redisUtils.deleteObject(redisUserKey);
    }

    public void deleteStudentCache(Long id) {
        String redisUserKey = "student::status::" + id;
        redisUtils.deleteObject(redisUserKey);
    }

    public void deleteTeacherCache(Long id) {
        String redisUserKey = "teacher::status::" + id;
        redisUtils.deleteObject(redisUserKey);
    }


}